package net.minecraft.client.audio;

import com.google.common.collect.*;
import io.netty.util.internal.ThreadLocalRandom;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import paulscode.sound.*;
import paulscode.sound.codecs.CodecJOrbis;
import paulscode.sound.libraries.LibraryLWJGLOpenAL;
import skidmonke.Minecraft;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class SoundManager {
    /**
     * The marker used for logging
     */
    private static final Marker LOG_MARKER = MarkerManager.getMarker("SOUNDS");
    private static final Logger logger = LogManager.getLogger();

    /**
     * A reference to the sound handler.
     */
    private final SoundHandler sndHandler;

    /**
     * Reference to the GameSettings object.
     */
    private final GameSettings options;

    /**
     * A reference to the sound system.
     */
    private SoundManager.SoundSystemStarterThread sndSystem;

    /**
     * Set to true when the SoundManager has been initialised.
     */
    private boolean loaded;

    /**
     * A counter for how long the sound manager has been running
     */
    private int playTime = 0;

    /**
     * Identifiers of all currently playing sounds. Type: HashBiMap<String, ISound>
     */
    private final Map playingSounds = HashBiMap.create();

    /**
     * Inverse map of currently playing sounds, automatically mirroring changes in original map
     */
    private final Map invPlayingSounds;

    /**
     * A HashMap<String, SoundPoolEntry> of the playing sounds.
     */
    private final Map playingSoundPoolEntries;

    /**
     * Contains sounds mapped by category. Type: Multimap<SoundCategory, String>
     */
    private final Multimap categorySounds;

    /**
     * A subset of playingSounds, this contains only ITickableSounds
     */
    private final List tickableSounds;

    /**
     * Contains sounds to play in n ticks.  Type: HashMap<ISound, Integer>
     */
    private final Map delayedSounds;

    /**
     * The future time in which to stop this sound.  Type: HashMap<String, Integer>
     */
    private final Map playingSoundsStopTime;
    // private static final String __OBFID = "CL_00001141";

    public SoundManager(SoundHandler p_i45119_1_, GameSettings p_i45119_2_) {
        this.invPlayingSounds = ((BiMap) this.playingSounds).inverse();
        this.playingSoundPoolEntries = Maps.newHashMap();
        this.categorySounds = HashMultimap.create();
        this.tickableSounds = Lists.newArrayList();
        this.delayedSounds = Maps.newHashMap();
        this.playingSoundsStopTime = Maps.newHashMap();
        this.sndHandler = p_i45119_1_;
        this.options = p_i45119_2_;

        try {
            SoundSystemConfig.addLibrary(LibraryLWJGLOpenAL.class);
            SoundSystemConfig.setCodec("ogg", CodecJOrbis.class);
        } catch (SoundSystemException var4) {
            logger.error(LOG_MARKER, "Error linking with the LibraryJavaSound plug-in", var4);
        }
    }

    public void reloadSoundSystem() {
        this.unloadSoundSystem();
        this.loadSoundSystem();
    }

    /**
     * Tries to add the paulscode library and the relevant codecs. If it fails, the master volume  will be set to zero.
     */
    private synchronized void loadSoundSystem() {
        if (!this.loaded) {
            try {
                (new Thread(new Runnable() {
                    // private static final String __OBFID = "CL_00001142";
                    public void run() {
                        SoundSystemConfig.setLogger(new SoundSystemLogger() {
                            // private static final String __OBFID = "CL_00002378";
                            public void message(String p_message_1_, int p_message_2_) {
                                if (!p_message_1_.isEmpty()) {
                                    SoundManager.logger.info(p_message_1_);
                                }
                            }

                            public void importantMessage(String p_importantMessage_1_, int p_importantMessage_2_) {
                                if (!p_importantMessage_1_.isEmpty()) {
                                    SoundManager.logger.warn(p_importantMessage_1_);
                                }
                            }

                            public void errorMessage(String p_errorMessage_1_, String p_errorMessage_2_, int p_errorMessage_3_) {
                                if (!p_errorMessage_2_.isEmpty()) {
                                    SoundManager.logger.error("Error in class '" + p_errorMessage_1_ + "'");
                                    SoundManager.logger.error(p_errorMessage_2_);
                                }
                            }
                        });
                        SoundManager.this.sndSystem = new SoundSystemStarterThread(null);
                        SoundManager.this.loaded = true;
                        SoundManager.this.sndSystem.setMasterVolume(SoundManager.this.options.getSoundLevel(SoundCategory.MASTER));
                        SoundManager.logger.info(SoundManager.LOG_MARKER, "Sound engine started");
                    }
                }, "Sound Library Loader")).start();
            } catch (RuntimeException var2) {
                logger.error(LOG_MARKER, "Error starting SoundSystem. Turning off sounds & music", var2);
                this.options.setSoundLevel(SoundCategory.MASTER, 0.0F);
                this.options.saveOptions();
            }
        }
    }

    /**
     * Returns the sound level (between 0.0 and 1.0) for a category, but 1.0 for the master sound category
     */
    private float getSoundCategoryVolume(SoundCategory p_148595_1_) {
        return p_148595_1_ != null && p_148595_1_ != SoundCategory.MASTER ? this.options.getSoundLevel(p_148595_1_) : 1.0F;
    }

    /**
     * Adjusts volume for currently playing sounds in this category
     */
    public void setSoundCategoryVolume(SoundCategory p_148601_1_, float p_148601_2_) {
        if (this.loaded) {
            if (p_148601_1_ == SoundCategory.MASTER) {
                this.sndSystem.setMasterVolume(p_148601_2_);
            } else {
                for (Object o : this.categorySounds.get(p_148601_1_)) {
                    String var4 = (String) o;
                    ISound var5 = (ISound) this.playingSounds.get(var4);
                    float var6 = this.getNormalizedVolume(var5, (SoundPoolEntry) this.playingSoundPoolEntries.get(var5), p_148601_1_);

                    if (var6 <= 0.0F) {
                        this.stopSound(var5);
                    } else {
                        this.sndSystem.setVolume(var4, var6);
                    }
                }
            }
        }
    }

    /**
     * Cleans up the Sound System
     */
    public void unloadSoundSystem() {
        if (this.loaded) {
            this.stopAllSounds();
            this.sndSystem.cleanup();
            this.loaded = false;
        }
    }

    /**
     * Stops all currently playing sounds
     */
    public void stopAllSounds() {
        if (this.loaded) {
            for (Object o : this.playingSounds.keySet()) {
                String var2 = (String) o;
                this.sndSystem.stop(var2);
            }

            this.playingSounds.clear();
            this.delayedSounds.clear();
            this.tickableSounds.clear();
            this.categorySounds.clear();
            this.playingSoundPoolEntries.clear();
            this.playingSoundsStopTime.clear();
        }
    }

    public void updateAllSounds() {
        ++this.playTime;
        Iterator var1 = this.tickableSounds.iterator();
        String var3;

        while (var1.hasNext()) {
            ITickableSound var2 = (ITickableSound) var1.next();
            var2.update();

            if (var2.isDonePlaying()) {
                this.stopSound(var2);
            } else {
                var3 = (String) this.invPlayingSounds.get(var2);
                this.sndSystem.setVolume(var3, this.getNormalizedVolume(var2, (SoundPoolEntry) this.playingSoundPoolEntries.get(var2), this.sndHandler.getSound(var2.getSoundLocation()).getSoundCategory()));
                this.sndSystem.setPitch(var3, this.getNormalizedPitch(var2, (SoundPoolEntry) this.playingSoundPoolEntries.get(var2)));
                this.sndSystem.setPosition(var3, var2.getXPosF(), var2.getYPosF(), var2.getZPosF());
            }
        }

        var1 = this.playingSounds.entrySet().iterator();
        ISound var4;

        while (var1.hasNext()) {
            Entry var9 = (Entry) var1.next();
            var3 = (String) var9.getKey();
            var4 = (ISound) var9.getValue();

            if (!this.sndSystem.playing(var3)) {
                int var5 = (Integer) this.playingSoundsStopTime.get(var3);

                if (var5 <= this.playTime) {
                    int var6 = var4.getRepeatDelay();

                    if (var4.canRepeat() && var6 > 0) {
                        this.delayedSounds.put(var4, this.playTime + var6);
                    }

                    var1.remove();
                    logger.debug(LOG_MARKER, "Removed channel {} because it's not playing anymore", var3);
                    this.sndSystem.removeSource(var3);
                    this.playingSoundsStopTime.remove(var3);
                    this.playingSoundPoolEntries.remove(var4);

                    try {
                        this.categorySounds.remove(this.sndHandler.getSound(var4.getSoundLocation()).getSoundCategory(), var3);
                    } catch (RuntimeException ignored) {
                    }

                    if (var4 instanceof ITickableSound) {
                        this.tickableSounds.remove(var4);
                    }
                }
            }
        }

        Iterator var10 = this.delayedSounds.entrySet().iterator();

        while (var10.hasNext()) {
            Entry var11 = (Entry) var10.next();

            if (this.playTime >= (Integer) var11.getValue()) {
                var4 = (ISound) var11.getKey();

                if (var4 instanceof ITickableSound) {
                    ((ITickableSound) var4).update();
                }

                this.playSound(var4);
                var10.remove();
            }
        }
    }

    /**
     * Returns true if the sound is playing or still within time
     */
    public boolean isSoundPlaying(ISound p_148597_1_) {
        if (!this.loaded) {
            return false;
        } else {
            String var2 = (String) this.invPlayingSounds.get(p_148597_1_);
            return var2 != null && (this.sndSystem.playing(var2) || this.playingSoundsStopTime.containsKey(var2) && (Integer) this.playingSoundsStopTime.get(var2) <= this.playTime);
        }
    }

    public void stopSound(ISound p_148602_1_) {
        if (this.loaded) {
            String var2 = (String) this.invPlayingSounds.get(p_148602_1_);

            if (var2 != null) {
                this.sndSystem.stop(var2);
            }
        }
    }

    public void playSound(ISound p_148611_1_) {
        if (this.loaded) {
            if (this.sndSystem.getMasterVolume() <= 0.0F) {
                logger.debug(LOG_MARKER, "Skipped playing soundEvent: {}, master volume was zero", p_148611_1_.getSoundLocation());
            } else {
                SoundEventAccessorComposite var2 = this.sndHandler.getSound(p_148611_1_.getSoundLocation());

                if (var2 == null) {
//                    logger.warn(LOG_MARKER, "Unable to play unknown soundEvent: {}", new Object[]{p_148611_1_.getSoundLocation()});
                } else {
                    SoundPoolEntry var3 = (SoundPoolEntry) var2.cloneEntry();

                    if (var3 == SoundHandler.missing_sound) {
                        logger.warn(LOG_MARKER, "Unable to play empty soundEvent: {}", var2.getSoundEventLocation());
                    } else {
                        float var4 = p_148611_1_.getVolume();
                        float var5 = 16.0F;

                        if (var4 > 1.0F) {
                            var5 *= var4;
                        }

                        SoundCategory var6 = var2.getSoundCategory();
                        float var7 = this.getNormalizedVolume(p_148611_1_, var3, var6);
                        double var8 = (double) this.getNormalizedPitch(p_148611_1_, var3);
                        ResourceLocation var10 = var3.getSoundPoolEntryLocation();

                        if (var7 == 0.0F) {
                            logger.debug(LOG_MARKER, "Skipped playing sound {}, volume was zero.", var10);
                        } else {
                            boolean var11 = p_148611_1_.canRepeat() && p_148611_1_.getRepeatDelay() == 0;
                            String var12 = MathHelper.func_180182_a(ThreadLocalRandom.current()).toString();

                            if (var3.isStreamingSound()) {
                                this.sndSystem.newStreamingSource(false, var12, getURLForSoundResource(var10), var10.toString(), var11, p_148611_1_.getXPosF(), p_148611_1_.getYPosF(), p_148611_1_.getZPosF(), p_148611_1_.getAttenuationType().getTypeInt(), var5);
                            } else {
                                this.sndSystem.newSource(false, var12, getURLForSoundResource(var10), var10.toString(), var11, p_148611_1_.getXPosF(), p_148611_1_.getYPosF(), p_148611_1_.getZPosF(), p_148611_1_.getAttenuationType().getTypeInt(), var5);
                            }

                            logger.debug(LOG_MARKER, "Playing sound {} for event {} as channel {}", var3.getSoundPoolEntryLocation(), var2.getSoundEventLocation(), var12);
                            this.sndSystem.setPitch(var12, (float) var8);
                            this.sndSystem.setVolume(var12, var7);
                            this.sndSystem.play(var12);
                            this.playingSoundsStopTime.put(var12, this.playTime + 20);
                            this.playingSounds.put(var12, p_148611_1_);
                            this.playingSoundPoolEntries.put(p_148611_1_, var3);

                            if (var6 != SoundCategory.MASTER) {
                                this.categorySounds.put(var6, var12);
                            }

                            if (p_148611_1_ instanceof ITickableSound) {
                                this.tickableSounds.add(p_148611_1_);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Normalizes pitch from parameters and clamps to [0.5, 2.0]
     */
    private float getNormalizedPitch(ISound p_148606_1_, SoundPoolEntry p_148606_2_) {
        return (float) MathHelper.clamp_double((double) p_148606_1_.getPitch() * p_148606_2_.getPitch(), 0.5D, 2.0D);
    }

    /**
     * Normalizes volume level from parameters.  Range [0.0, 1.0]
     */
    private float getNormalizedVolume(ISound p_148594_1_, SoundPoolEntry p_148594_2_, SoundCategory p_148594_3_) {
        return (float) MathHelper.clamp_double((double) p_148594_1_.getVolume() * p_148594_2_.getVolume(), 0.0D, 1.0D) * this.getSoundCategoryVolume(p_148594_3_);
    }

    /**
     * Pauses all currently playing sounds
     */
    public void pauseAllSounds() {
        for (Object o : this.playingSounds.keySet()) {
            String var2 = (String) o;
            logger.debug(LOG_MARKER, "Pausing channel {}", var2);
            this.sndSystem.pause(var2);
        }
    }

    /**
     * Resumes playing all currently playing sounds (after pauseAllSounds)
     */
    public void resumeAllSounds() {
        for (Object o : this.playingSounds.keySet()) {
            String var2 = (String) o;
            logger.debug(LOG_MARKER, "Resuming channel {}", var2);
            this.sndSystem.play(var2);
        }
    }

    /**
     * Adds a sound to play in n tick
     */
    public void playDelayedSound(ISound p_148599_1_, int p_148599_2_) {
        this.delayedSounds.put(p_148599_1_, this.playTime + p_148599_2_);
    }

    private static URL getURLForSoundResource(final ResourceLocation p_148612_0_) {
        String var1 = String.format("%s:%s:%s", "mcsounddomain", p_148612_0_.getResourceDomain(), p_148612_0_.getResourcePath());
        URLStreamHandler var2 = new URLStreamHandler() {
            // private static final String __OBFID = "CL_00001143";
            protected URLConnection openConnection(final URL p_openConnection_1_) {
                return new URLConnection(p_openConnection_1_) {
                    // private static final String __OBFID = "CL_00001144";
                    public void connect() {
                    }

                    public InputStream getInputStream() throws IOException {
                        return Minecraft.getMinecraft().getResourceManager().getResource(p_148612_0_).getInputStream();
                    }
                };
            }
        };

        try {
            return new URL((URL) null, var1, var2);
        } catch (MalformedURLException var4) {
            throw new Error("TODO: Sanely handle url exception! :D");
        }
    }

    /**
     * Sets the listener of sounds
     */
    public void setListener(EntityPlayer p_148615_1_, float p_148615_2_) {
        if (this.loaded && p_148615_1_ != null) {
            float var3 = p_148615_1_.prevRotationPitch + (p_148615_1_.rotationPitch - p_148615_1_.prevRotationPitch) * p_148615_2_;
            float var4 = p_148615_1_.prevRotationYaw + (p_148615_1_.rotationYaw - p_148615_1_.prevRotationYaw) * p_148615_2_;
            double var5 = p_148615_1_.prevPosX + (p_148615_1_.posX - p_148615_1_.prevPosX) * (double) p_148615_2_;
            double var7 = p_148615_1_.prevPosY + (p_148615_1_.posY - p_148615_1_.prevPosY) * (double) p_148615_2_ + (double) p_148615_1_.getEyeHeight();
            double var9 = p_148615_1_.prevPosZ + (p_148615_1_.posZ - p_148615_1_.prevPosZ) * (double) p_148615_2_;
            float var11 = MathHelper.cos((var4 + 90.0F) * 0.017453292F);
            float var12 = MathHelper.sin((var4 + 90.0F) * 0.017453292F);
            float var13 = MathHelper.cos(-var3 * 0.017453292F);
            float var14 = MathHelper.sin(-var3 * 0.017453292F);
            float var15 = MathHelper.cos((-var3 + 90.0F) * 0.017453292F);
            float var16 = MathHelper.sin((-var3 + 90.0F) * 0.017453292F);
            float var17 = var11 * var13;
            float var19 = var12 * var13;
            float var20 = var11 * var15;
            float var22 = var12 * var15;
            this.sndSystem.setListenerPosition((float) var5, (float) var7, (float) var9);
            this.sndSystem.setListenerOrientation(var17, var14, var19, var20, var16, var22);
        }
    }

    static class SoundSystemStarterThread extends SoundSystem {
        // private static final String __OBFID = "CL_00001145";

        private SoundSystemStarterThread() {
        }

        public boolean playing(String p_playing_1_) {

            synchronized (SoundSystemConfig.THREAD_SYNC) {
                if (this.soundLibrary == null) {
                    return false;
                } else {
                    Source var3 = this.soundLibrary.getSources().get(p_playing_1_);
                    return var3 != null && (var3.playing() || var3.paused() || var3.preLoad);
                }
            }
        }

        SoundSystemStarterThread(Object p_i45118_2_) {
            this();
        }
    }
}
